<script lang="tsx">
import type { ContextMenuItem, ItemContentProps, Axis } from './typing';
import type { FunctionalComponent, CSSProperties, PropType } from 'vue';
import { defineComponent, nextTick, onMounted, computed, ref, unref, onUnmounted } from 'vue';
import { Menu, Divider } from 'ant-design-vue';
import Icon from '../../Icon';

const prefixCls = 'context-menu';

const props = {
	width: { type: Number, default: 156 },
	customEvent: { type: Object as PropType<Event>, default: null },
	styles: { type: Object as PropType<CSSProperties> },
	showIcon: { type: Boolean, default: true },
	axis: {
		// The position of the right mouse button click
		type: Object as PropType<Axis>,
		default() {
			return { x: 0, y: 0 };
		},
	},
	items: {
		// The most important list, if not, will not be displayed
		type: Array as PropType<ContextMenuItem[]>,
		default() {
			return [];
		},
	},
};

const ItemContent: FunctionalComponent<ItemContentProps> = props => {
	const { item } = props;
	return (
		<span style="display: inline-block; width: 100%; " class="px-4" onClick={props.handler.bind(null, item)}>
			{props.showIcon && item.icon && <Icon class="mr-2" icon={item.icon} />}
			<span>{item.label}</span>
		</span>
	);
};

export default defineComponent({
	name: 'ContextMenu',
	props,
	setup(props) {
		const wrapRef = ref(null);
		const showRef = ref(false);

		const getStyle = computed((): CSSProperties => {
			const { axis, items, styles, width } = props;
			const { x, y } = axis || { x: 0, y: 0 };
			const menuHeight = (items || []).length * 40;
			const menuWidth = width;
			const body = document.body;

			const left = body.clientWidth < x + menuWidth ? x - menuWidth : x;
			const top = body.clientHeight < y + menuHeight ? y - menuHeight : y;
			return {
				...styles,
				position: 'absolute',
				width: `${width}px`,
				left: `${left + 1}px`,
				top: `${top + 1}px`,
				zIndex: 9999,
			};
		});

		onMounted(() => {
			nextTick(() => (showRef.value = true));
		});

		onUnmounted(() => {
			const el = unref(wrapRef);
			el && document.body.removeChild(el);
		});

		function handleAction(item: ContextMenuItem, e: MouseEvent) {
			const { handler, disabled } = item;
			if (disabled) {
				return;
			}
			showRef.value = false;
			e?.stopPropagation();
			e?.preventDefault();
			handler?.();
		}

		function renderMenuItem(items: ContextMenuItem[]) {
			const visibleItems = items.filter(item => !item.hidden);
			return visibleItems.map(item => {
				const { disabled, label, children, divider = false } = item;

				const contentProps = {
					item,
					handler: handleAction,
					showIcon: props.showIcon,
				};

				if (!children || children.length === 0) {
					return (
						<>
							<Menu.Item disabled={disabled} class={`${prefixCls}__item`} key={label}>
								<ItemContent {...contentProps} />
							</Menu.Item>
							{divider ? <Divider key={`d-${label}`} /> : null}
						</>
					);
				}
				if (!unref(showRef)) return null;

				return (
					<Menu.SubMenu key={label} disabled={disabled} popupClassName={`${prefixCls}__popup`}>
						{{
							title: () => <ItemContent {...contentProps} />,
							default: () => renderMenuItem(children),
						}}
					</Menu.SubMenu>
				);
			});
		}
		return () => {
			if (!unref(showRef)) {
				return null;
			}
			const { items } = props;
			return (
				<div class={prefixCls}>
					<Menu inlineIndent={12} mode="vertical" ref={wrapRef} style={unref(getStyle)}>
						{renderMenuItem(items)}
					</Menu>
				</div>
			);
		};
	},
});
</script>
<style lang="less">
@default-height: 42px !important;

@small-height: 36px !important;

@large-height: 36px !important;

.item-style() {
	li {
		display: inline-block;
		width: 100%;
		height: @default-height;
		margin: 0 !important;
		line-height: @default-height;

		span {
			line-height: @default-height;
		}

		> div {
			margin: 0 !important;
		}

		&:not(.ant-menu-item-disabled):hover {
			color: @text-color-base;
			background-color: @item-hover-bg;
		}
	}
}

.context-menu {
	position: fixed;
	top: 0;
	left: 0;
	z-index: 200;
	display: block;
	width: 156px;
	margin: 0;
	list-style: none;
	background-color: @component-background;
	border: 1px solid rgb(0 0 0 / 8%);
	border-radius: 0.25rem;
	box-shadow: 0 2px 2px 0 rgb(0 0 0 / 14%), 0 3px 1px -2px rgb(0 0 0 / 10%), 0 1px 5px 0 rgb(0 0 0 / 6%);
	background-clip: padding-box;
	user-select: none;

	&__item {
		margin: 0 !important;
	}
	.item-style();

	.ant-divider {
		margin: 0;
	}

	&__popup {
		.ant-divider {
			margin: 0;
		}

		.item-style();
	}

	.ant-menu-submenu-title,
	.ant-menu-item {
		padding: 0 !important;
	}
}
</style>
